#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
#endregion

namespace XnaGameComponents
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class FreelookCamera : DrawableGameComponent,Camera
    {
        public FreelookCamera(Game game)
            : base(game)
        {
            // TODO: Construct any child components here
        }

        private bool cameraStateKeyDown;
        private int cameraState;
        private float avatarYaw;
        private float avatarXaw;
        // Set rates in world units per 1/60th second (the default fixed-step interval).
        private float rotationSpeed = 1f / 60f;
        //float forwardSpeed = 500f / 60f;
        private float forwardSpeed = 500f / 60f;
        private int fastFactor=5;
        protected Vector3 avatarPosition = new Vector3(0, 2, -20);
        Vector3 cameraReference = new Vector3(0, 0, 10);
        static float viewAngle = MathHelper.PiOver4;
        static float nearClip = 0.1f;
        static float farClip = 65536f;
        MouseState mousePos;
        private bool showCoordinates;

        public bool ShowCoordinates
        {
            get { return showCoordinates; }
            set { showCoordinates = value; }
        }



        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here

            base.Initialize();
        }

        protected override void LoadContent()
        {
            base.LoadContent();
            Mouse.SetPosition((int)(GraphicsDevice.Viewport.Width / 2f), (int)(GraphicsDevice.Viewport.Y / 2f));
        }


        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here
            GetCurrentCamera();
            UpdateAvatarPosition();
            base.Update(gameTime);
        }

        private void GetCurrentCamera()
        {
            KeyboardState keyboardState = Keyboard.GetState();
            GamePadState currentState = GamePad.GetState(PlayerIndex.One);

            // Toggle the state of the camera.
            if (keyboardState.IsKeyDown(Keys.Tab) || (currentState.Buttons.LeftShoulder == ButtonState.Pressed))
            {
                cameraStateKeyDown = true;
            }
            else if (cameraStateKeyDown == true)
            {
                cameraStateKeyDown = false;
                cameraState += 1;
                cameraState %= 3;
            }
        }

        private void UpdateAvatarPosition()
        {
            KeyboardState keyboardState = Keyboard.GetState();
            GamePadState currentState = GamePad.GetState(PlayerIndex.One);
            MouseState mouseState = Mouse.GetState();

            if (mousePos == null)
                mousePos = mouseState;
            avatarYaw += (mousePos.X - mouseState.X)*rotationSpeed/10;
            avatarXaw -= (mousePos.Y - mouseState.Y)*rotationSpeed/10;
            Mouse.SetPosition(GraphicsDevice.Viewport.X, GraphicsDevice.Viewport.Y);
            IGameConfiguration config = (IGameConfiguration)Game.Services.GetService(typeof(IGameConfiguration));

            if (keyboardState.IsKeyDown(config["Left"]) || (currentState.DPad.Left == ButtonState.Pressed))
            {
                Vector3 v;
                if(keyboardState.IsKeyDown(Keys.LeftShift))
                    v = new Vector3(forwardSpeed * fastFactor, 0, 0);
                else
                    v = new Vector3(forwardSpeed, 0, 0);
                Matrix sideMovement = Matrix.CreateRotationX(avatarXaw) * Matrix.CreateRotationY(avatarYaw);
                v = Vector3.Transform(v, sideMovement);
                avatarPosition.Z += v.Z;
                avatarPosition.X += v.X;
            }
            if (keyboardState.IsKeyDown(config["Right"]) || (currentState.DPad.Right == ButtonState.Pressed))
            {
                Vector3 v;
                if (keyboardState.IsKeyDown(Keys.LeftShift))
                    v = new Vector3(-forwardSpeed * fastFactor, 0, 0);
                else
                    v = new Vector3(-forwardSpeed, 0, 0);
                Matrix sideMovement = Matrix.CreateRotationX(avatarXaw) * Matrix.CreateRotationY(avatarYaw);
                v = Vector3.Transform(v, sideMovement);
                avatarPosition.Z += v.Z;
                avatarPosition.X += v.X;
            }
            if (keyboardState.IsKeyDown(config["Up"]) || (currentState.DPad.Up == ButtonState.Pressed))
            {
                Vector3 v;
                if (keyboardState.IsKeyDown(Keys.LeftShift))
                    v = new Vector3(0, 0, forwardSpeed * fastFactor);
                else
                    v = new Vector3(0, 0, forwardSpeed);
                Matrix forwardMovement = Matrix.CreateRotationX(avatarXaw) * Matrix.CreateRotationY(avatarYaw);
                v = Vector3.Transform(v, forwardMovement);
                avatarPosition.Z += v.Z;
                avatarPosition.X += v.X;
            }
            if (keyboardState.IsKeyDown(config["Down"]) || (currentState.DPad.Down == ButtonState.Pressed))
            {
                Vector3 v;
                if (keyboardState.IsKeyDown(Keys.LeftShift))
                    v = new Vector3(0, 0, -forwardSpeed * fastFactor);
                else
                    v = new Vector3(0, 0, -forwardSpeed);
                Matrix forwardMovement = Matrix.CreateRotationX(avatarXaw) * Matrix.CreateRotationY(avatarYaw);
                v = Vector3.Transform(v, forwardMovement);
                avatarPosition.Z += v.Z;
                avatarPosition.X += v.X;
            }
        }

        public override void Draw(GameTime gameTime)
        {
            UpdateCamera();
            World = Matrix.CreateRotationX(avatarXaw) * Matrix.CreateRotationY(avatarYaw) * Matrix.CreateTranslation(avatarPosition);
            if (showCoordinates)
            {
                ContentManager content = new ContentManager(Game.Services);
                SpriteBatch sprite = new SpriteBatch(GraphicsDevice);
                SpriteFont font = content.Load<SpriteFont>("Times");
                string s=String.Format("X: {0}\nY: {1}\nZ: {2}", avatarPosition.X,avatarPosition.Y,avatarPosition.Z);
                sprite.Begin();
                sprite.DrawString(font, s, font.MeasureString(s) / 2, Color.LightGreen);
                sprite.End();
            }
            base.Draw(gameTime);
        }

        void UpdateCamera()
        {
            // Calculate the camera's current position.
            Vector3 cameraPosition = avatarPosition;


            // Calculate the position the camera is looking at.
            Vector3 cameraLookat = cameraPosition + Target;

            // Set up the view matrix and projection matrix.
            View = Matrix.CreateLookAt(cameraPosition, cameraLookat, new Vector3(0.0f, 1.0f, 0.0f));

            Viewport viewport = GraphicsDevice.Viewport;
            float aspectRatio = (float)viewport.Width / (float)viewport.Height;

            Projection = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, nearClip, farClip);
        }

        #region Camera Membres

        private Matrix world;

        public Matrix World
        {
            get { return world; }
            set { world = value; }
        }

        private Matrix view;

        public Matrix View
        {
            get { return view; }
            set { view = value; }
        }

        private Matrix proj;

        public Matrix Projection
        {
            get { return proj; }
            set { proj = value; }
        }

        public Vector3 Target
        {
            get
            {
                Matrix rotationMatrix = Matrix.CreateRotationX(avatarXaw) * Matrix.CreateRotationY(avatarYaw);

                // Create a vector pointing the direction the camera is facing.
                return Vector3.Transform(cameraReference, rotationMatrix);
            }
        }



        public Vector3 Position
        {
            get { return avatarPosition; }
            set { avatarPosition = value; }
        }

        public void Move(Vector3 v, bool relative)
        {
            if (relative)
                MoveRelative(v);
            else
                MoveAbsolute(v);
        }

        public void MoveRelative(Vector3 v)
        {
            avatarPosition += v;
        }

        public void MoveAbsolute(Vector3 v)
        {
            avatarPosition = v;
        }

        #endregion

        #region IPositionnable Membres

        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private XnaDesignerGenerator.XnaGameComponent site;

        public XnaDesignerGenerator.XnaGameComponent TargetObject
        {
            get { return site; }
            set { site = value; }
        }

        #endregion
    }
}


